/**************************************************************************//**
 * @file     ehci_iso.c
 * @brief   USB EHCI isochronous transfer driver.
 *
 * SPDX-License-Identifier: Apache-2.0
 * @copyright (C) 2023 Nuvoton Technology Corp. All rights reserved.
*****************************************************************************/

/// @cond HIDDEN_SYMBOLS

static uint32_t  g_flr_cnt;                        /* frame list rollover counter                */

static const uint16_t sitd_OUT_Smask [] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f };

static int ehci_iso_split_xfer(UTR_T *utr, ISO_EP_T *iso_ep);

/*
 *  Inspect the iTD can be reclaimed or not. If yes, collect the transaction results.
 *  Return:  1 - reclaimed
 *           0 - not completed
 */
static int  review_itd(iTD_T *itd)
{
	UTR_T      *utr;
	uint32_t   frnidx = itd->sched_frnidx;
	uint32_t   now_frame = (_ehci->UFINDR >> 3) & 0x3FF;
	int        i, fidx;

	// sysprintf("R - %d %d, 0x%x\n", now_frame, frnidx, itd->Transaction[0]);

	if (now_frame == frnidx)
	{
		for (i = 0; i < 8; i++)
		{
			if (itd->Transaction[i] & ITD_STATUS_ACTIVE)
				return 0;                   /* have any not completed frames              */
		}
	}
	else if (now_frame > frnidx)
	{
		if ((now_frame - frnidx) > EHCI_ISO_RCLM_RANGE)
			return 0;                       /* don't touch it                             */
	}
	else
	{
		if (now_frame + FL_SIZE - frnidx > EHCI_ISO_RCLM_RANGE)
			return 0;                       /* don't touch it                             */
	}

	/*
	 *  Reclaim this iTD
	 */
	utr = itd->utr;
	fidx = itd->fidx;
	for (i = 0; i < 8; i++)
	{
		if (!(itd->trans_mask & (0x1<<i)))
			continue;                       /* not scheduled micro-frame                  */

		if (ITD_STATUS(itd->Transaction[i]))
		{
			if (itd->Transaction[i] & ITD_STATUS_ACTIVE)
			{
				utr->iso_status[fidx] = USBH_ERR_NOT_ACCESS0;
				utr->status = USBH_ERR_NOT_ACCESS0;
			}
			else if (itd->Transaction[i] & ITD_STATUS_BABBLE)
			{
				utr->iso_status[fidx] = USBH_ERR_BABBLE_DETECTED;
				utr->status = USBH_ERR_TRANSFER;
			}
			else if (itd->Transaction[i] & ITD_STATUS_BUFF_ERR)
			{
				utr->iso_status[fidx] = USBH_ERR_DATA_BUFF;
				utr->status = USBH_ERR_TRANSFER;
			}
			else
			{
				utr->iso_status[fidx] = USBH_ERR_TRANSACTION;
				utr->status = USBH_ERR_TRANSFER;
			}
		}
		else
		{
			utr->iso_status[fidx] = 0;
			utr->iso_xlen[fidx] = ITD_XFER_LEN(itd->Transaction[i]);
		}
		fidx++;
	}
	utr->td_cnt--;

	if (utr->td_cnt == 0)                   /* All iTD of this UTR done                   */
	{
		utr->bIsTransferDone = 1;
		if (!IS_NULL_PTR(utr->func))
			utr->func(utr);
	}

	return 1;                               /* to be reclaimed                            */
}

/*
 *  Inspect the siTD can be reclaimed or not. If yes, collect the transaction results.
 *  Return:  1 - reclaimed
 *           0 - not completed
 */
static int  review_sitd(siTD_T *sitd)
{
	UTR_T      *utr;
	uint32_t   frnidx = sitd->sched_frnidx;
	uint32_t   now_frame = (_ehci->UFINDR >> 3) & 0x3FF;
	int        fidx;
	uint32_t   TotalBytesToTransfer;

	if (now_frame == frnidx)
	{
		if (SITD_STATUS(sitd->StsCtrl) == SITD_STATUS_ACTIVE)
			return 0;
	}
	else if (now_frame > frnidx)
	{
		if ((now_frame - frnidx) > EHCI_ISO_RCLM_RANGE)
			return 0;                       /* don't touch it                             */
	}
	else
	{
		if (now_frame + FL_SIZE - frnidx > EHCI_ISO_RCLM_RANGE)
			return 0;                       /* don't touch it                             */
	}

	/*
	 *  Reclaim this siTD
	 */
	utr = sitd->utr;
	fidx = sitd->fidx;

	if (SITD_STATUS(sitd->StsCtrl))
	{
		if (sitd->StsCtrl & SITD_STATUS_ACTIVE)
		{
			utr->iso_status[fidx] = USBH_ERR_NOT_ACCESS0;
		}
		else if (sitd->StsCtrl & SITD_BABBLE_DETECTED)
		{
			utr->iso_status[fidx] = USBH_ERR_BABBLE_DETECTED;
			utr->status = USBH_ERR_TRANSFER;
		}
		else if (sitd->StsCtrl & SITD_STATUS_BUFF_ERR)
		{
			utr->iso_status[fidx] = USBH_ERR_DATA_BUFF;
			utr->status = USBH_ERR_TRANSFER;
		}
		else
		{
			utr->iso_status[fidx] = USBH_ERR_TRANSACTION;
			utr->status = USBH_ERR_TRANSFER;
		}
	}
	else
	{
		TotalBytesToTransfer = (sitd->StsCtrl & SITD_XFER_CNT_Msk) >> SITD_XFER_CNT_Pos;
		utr->iso_xlen[fidx] =  utr->iso_xlen[fidx] - TotalBytesToTransfer;
		utr->iso_status[fidx] = 0;
	}
	utr->td_cnt--;

	if (utr->td_cnt == 0)                   /* All iTD of this UTR done                   */
	{
		utr->bIsTransferDone = 1;
		if (!IS_NULL_PTR(utr->func))
			utr->func(utr);
	}
	return 1;                               /* to be reclaimed                            */
}

/*
 *  Some iTD/siTD may be scheduled but not serviced due to time missed.
 *  This function scan several earlier frames and drop unserviced iTD/siTD if found.
 */
static void scan_isochronous_list(void)
{
	ISO_EP_T   *iso_ep = iso_ep_list;
	iTD_T      *itd, *itd_pre, *p;
	siTD_T     *sitd, *sitd_pre, *sp;
	uint32_t   frnidx;

	DISABLE_EHCI_IRQ();

	while (!IS_NULL_PTR(iso_ep))            /* Search all activated iso endpoints         */
	{
		/*--------------------------------------------------------------------------------*/
		/*  Scan all iTDs                                                                 */
		/*--------------------------------------------------------------------------------*/
		itd = nc_ptr(iso_ep->itd_list);     /* get the first iTD from iso_ep's iTD list   */
		itd_pre = NULL;
		while (!IS_NULL_PTR(itd))           /* traverse all iTDs of itd list              */
		{
			if (review_itd(itd))            /* inspect and reclaim iTD                    */
			{
				/*------------------------------------------------------------------------*/
				/*  Remove this iTD from period frame list                                */
				/*------------------------------------------------------------------------*/
				frnidx = itd->sched_frnidx;
				if (_PFList[frnidx] == ITD_HLNK_ITD(itd))
				{
					/* is the first entry, just change to next     */
					_PFList[frnidx] = itd->Next_Link;
				}
				else
				{
					p = ITD_PTR(_PFList[frnidx]);     /* find the preceding iTD            */
					while ((ITD_PTR(p->Next_Link) != itd) && !IS_NULL_PTR(p))
					{
						p = ITD_PTR(p->Next_Link);
					}

					if (IS_NULL_PTR(p))               /* link list out of control!         */
					{
						USB_error("An iTD lost refernece to periodic frame list! 0x%x -> %d\n", (int64_t)itd, frnidx);
					}
					else                              /* remove iTD from list              */
					{
						p->Next_Link = itd->Next_Link;
					}
				}

				/*------------------------------------------------------------------------*/
				/*  Remove this iTD from iso_ep's iTD list                                */
				/*------------------------------------------------------------------------*/
				if (IS_NULL_PTR(itd_pre))
					iso_ep->itd_list = itd->next;
				else
					itd_pre->next = itd->next;

				p = itd->next;
				free_ehci_iTD(itd);
				itd = p;
			}
			else
			{
				itd_pre = itd;
				itd = itd->next;            /* traverse to the next iTD of iTD list       */
			}
		}

		/*--------------------------------------------------------------------------------*/
		/*  Scan all siTDs                                                                */
		/*--------------------------------------------------------------------------------*/
		sitd = iso_ep->sitd_list;           /* get the first siTD from iso_ep's siTD list */
		sitd_pre = NULL;
		while (!IS_NULL_PTR(sitd))          /* traverse all siTDs of sitd list            */
		{
			if (review_sitd(sitd))          /* inspect and reclaim siTD                   */
			{
				/*------------------------------------------------------------------------*/
				/*  Remove this siTD from period frame list                               */
				/*------------------------------------------------------------------------*/
				frnidx = sitd->sched_frnidx;
				if (_PFList[frnidx] == SITD_HLNK_SITD(sitd))
				{
					/* is the first entry, just change to next     */
					_PFList[frnidx] = sitd->Next_Link;
				}
				else
				{
					sp = SITD_PTR(_PFList[frnidx]);   /* find the preceding siTD           */
					while ((SITD_PTR(sp->Next_Link) != sitd) && !IS_NULL_PTR(sp))
					{
						sp = SITD_PTR(sp->Next_Link);
					}

					if (IS_NULL_PTR(sp))              /* link list out of control!         */
					{
						USB_error("An siTD lost reference to periodic frame list! 0x%x -> %d\n", (int64_t)sitd, frnidx);
					}
					else                              /* remove iTD from list              */
					{
						sp->Next_Link = sitd->Next_Link;
					}
				}

				/*------------------------------------------------------------------------*/
				/*  Remove this siTD from iso_ep's siTD list                              */
				/*------------------------------------------------------------------------*/
				if (IS_NULL_PTR(sitd_pre))
				{
					iso_ep->sitd_list = sitd->next;
				}
				else
				{
					sitd_pre->next = sitd->next;
				}
				sp = sitd->next;
				free_ehci_siTD(sitd);
				sitd = sp;
			}
			else
			{
				sitd_pre = sitd;
				sitd = sitd->next;           /* traverse to the next siTD of siTD list     */
			}
		}
		iso_ep = iso_ep->next;
	}
	ENABLE_EHCI_IRQ();
}


static void  write_itd_info(UTR_T *utr, iTD_T *itd)
{
	UDEV_T     *udev = utr->udev;
	EP_INFO_T  *ep = utr->ep;               /* reference to isochronous endpoint          */
	uint32_t   buff_page_addr;
	int        i;

	buff_page_addr = itd->buff_base & 0xFFFFF000;     /* 4K page                          */

	for (i = 0; i < 7; i++)
	{
		itd->Bptr[i] = buff_page_addr + (0x1000 * i);
	}
	/* EndPtr  R  Device Address        */
	itd->Bptr[0] |= (udev->dev_num) | ((ep->bEndpointAddress & 0xF) << ITD_EP_NUM_Pos);
	itd->Bptr[1] |= ep->wMaxPacketSize;               /* Maximum Packet Size              */

	if ((ep->bEndpointAddress & EP_ADDR_DIR_MASK) == EP_ADDR_DIR_IN) /* I/O               */
		itd->Bptr[1] |= ITD_DIR_IN;
	else
		itd->Bptr[1] |= ITD_DIR_OUT;

	itd->Bptr[2] |= (ep->wMaxPacketSize + 1023)/1024;                /* Mult              */
}

static void  write_itd_micro_frame(UTR_T *utr, int fidx, iTD_T *itd, int mf)
{
	uint32_t   buff_addr;

	buff_addr = ptr_to_u32(utr->iso_buff[fidx]);  /* xfer buffer start address of this frame */

	itd->Transaction[mf] = ITD_STATUS_ACTIVE |                                            /* Status */
						   ((utr->iso_xlen[fidx] & 0xFFF) << ITD_XLEN_Pos) |              /* Transaction Length */
						   ((buff_addr & 0xFFFFF000) - (itd->buff_base & 0xFFFFF000)) |   /* PG */
						   (buff_addr & 0xFFF);                                           /* Transaction offset */
}


static void remove_iso_ep_from_list(ISO_EP_T *iso_ep)
{
	ISO_EP_T  *p;

	if (iso_ep_list == iso_ep)
	{
		iso_ep_list = iso_ep->next;         /* it's the first entry, remove it            */
		return;
	}

	p = iso_ep_list;                        /* find the previous entry of iso_ep          */
	while (!IS_NULL_PTR(p->next))
	{
		if (p->next == iso_ep)
		{
			break;
		}
		p = p->next;
	}

	if (IS_NULL_PTR(p->next))
	{
		return;                             /* not found                                  */
	}
	p->next = iso_ep->next;                 /* remove iso_ep from list                    */
}


static __inline void  add_itd_to_iso_ep(ISO_EP_T *iso_ep, iTD_T *itd)
{
	iTD_T   *p;

	itd->next = NULL;

	if (IS_NULL_PTR(iso_ep->itd_list))
	{
		iso_ep->itd_list = itd;
		return;
	}

	/*
	 * Find the tail entry of iso_ep->itd_list
	 */
	p = iso_ep->itd_list;
	while (!IS_NULL_PTR(p->next))
	{
		p = p->next;
	}
	p->next = itd;
}

static int ehci_iso_xfer(UTR_T *utr)
{
	EP_INFO_T  *ep = utr->ep;               /* reference to isochronous endpoint          */
	ISO_EP_T   *iso_ep;                     /* software iso endpoint descriptor           */
	iTD_T      *itd, *itd_next, *itd_list = NULL;
	int        i, itd_cnt;
	int        trans_mask;                  /* bit mask of used xfer in an iTD            */
	int        fidx;                        /* index to the 8 iso frames of UTR           */
	int        interval;                    /* frame interval of iTD                      */

	if (!IS_NULL_PTR(ep->hw_pipe))
	{
		iso_ep = nc_ptr(ep->hw_pipe);       /* get reference of the isochronous endpoint  */

		if (utr->bIsoNewSched)
			iso_ep->next_frame = (((_ehci->UFINDR + (EHCI_ISO_DELAY * 8)) & HSUSBH_UFINDR_FI_Msk) >> 3) & 0x3FF;
	}
	else
	{
		/* first time transfer of this iso endpoint   */
		iso_ep = usbh_alloc_mem(sizeof(*iso_ep));
		if (IS_NULL_PTR(iso_ep))
			return USBH_ERR_MEMORY_OUT;

		memset(iso_ep, 0, sizeof(*iso_ep));
		iso_ep->ep = ep;
		iso_ep->next_frame = (((_ehci->UFINDR + (EHCI_ISO_DELAY * 8)) & HSUSBH_UFINDR_FI_Msk) >> 3) & 0x3FF;

		ep->hw_pipe = iso_ep;

		/*
		 *  Add this iso_ep into iso_ep_list
		 */
		DISABLE_EHCI_IRQ();
		iso_ep->next = iso_ep_list;
		iso_ep_list = iso_ep;
		ENABLE_EHCI_IRQ();
	}

	if (utr->udev->speed == SPEED_FULL)
		return ehci_iso_split_xfer(utr, iso_ep);

	/*------------------------------------------------------------------------------------*/
	/*  Allocate iTDs                                                                     */
	/*------------------------------------------------------------------------------------*/

	if (ep->bInterval < 2)                  /* transfer interval is 1 micro-frame         */
	{
		trans_mask = 0xFF;
		itd_cnt = 1;                        /* required 1 iTD for one UTR                 */
		interval = 1;                       /* iTD frame interval of this endpoint        */
	}
	else if (ep->bInterval < 4)             /* transfer interval is 2 micro-frames        */
	{
		trans_mask = 0x55;
		itd_cnt = 2;                        /* required 2 iTDs for one UTR                */
		interval = 1;                       /* iTD frame interval of this endpoint        */
	}
	else if (ep->bInterval < 8)             /* transfer interval is 4 micro-frames        */
	{
		trans_mask = 0x44;
		itd_cnt = 4;                        /* required 4 iTDs for one UTR                */
		interval = 1;                       /* iTD frame interval of this endpoint        */
	}
	else if (ep->bInterval < 16)            /* transfer interval is 8 micro-frames        */
	{
		trans_mask = 0x08;                  /* there's 1 transfer in one iTD              */
		itd_cnt = 8;                        /* required 8 iTDs for one UTR                */
		interval = 1;                       /* iTD frame interval of this endpoint        */
	}
	else if (ep->bInterval < 32)            /* transfer interval is 16 micro-frames       */
	{
		trans_mask = 0x10;                  /* there's 1 transfer in one iTD              */
		itd_cnt = 8;                        /* required 8 iTDs for one UTR                */
		interval = 2;                       /* iTD frame interval of this endpoint        */
	}
	else if (ep->bInterval < 64)            /* transfer interval is 32 micro-frames       */
	{
		trans_mask = 0x02;                  /* there's 1 transfer in one iTD              */
		itd_cnt = 8;                        /* required 8 iTDs for one UTR                */
		interval = 4;                       /* iTD frame interval of this endpoint        */
	}
	else                                    /* transfer interval is 64 micro-frames       */
	{
		trans_mask = 0x04;                  /* there's 1 transfer in one iTD              */
		itd_cnt = 8;                        /* required 8 iTDs for one UTR                */
		interval = 8;                       /* iTD frame interval of this endpoint        */
	}

	for (i = 0; i < itd_cnt; i++)           /* allocate all iTDs required by UTR          */
	{
		itd = alloc_ehci_iTD();
		if (IS_NULL_PTR(itd))
			goto malloc_failed;

		if (IS_NULL_PTR(itd_list))          /* link all iTDs                              */
		{
			itd_list = itd;
		}
		else
		{
			itd->next = itd_list;
			itd_list = itd;
		}
	}

	utr->td_cnt = itd_cnt;

	/*------------------------------------------------------------------------------------*/
	/*  Fill and link all iTDs                                                            */
	/*------------------------------------------------------------------------------------*/

	utr->iso_sf = iso_ep->next_frame;
	fidx = 0;                               /* index to UTR iso frmes (total IF_PER_UTR)  */

	for (itd = itd_list; !IS_NULL_PTR(itd); )
	{
		if (fidx >= IF_PER_UTR)             /* unlikely                                   */
		{
			USB_error("EHCI driver ITD bug!?\n");
			goto malloc_failed;
		}

		itd->utr = utr;
		itd->fidx = fidx;                   /* index to UTR's n'th IF_PER_UTR frame       */
		itd->buff_base = ptr_to_u32(utr->iso_buff[fidx]);    /* iTD buffer base is buffer of the first UTR iso frame serviced by this iTD */
		itd->trans_mask = trans_mask;

		write_itd_info(utr, itd);

		for (i = 0; i < 8; i++)             /* settle xfer into micro-frames              */
		{
			if (!(trans_mask & (0x1 << i)))
			{
				itd->Transaction[i] = 0;    /* not accesed                                */
				continue;                   /* not scheduled micro-frame                  */
			}

			write_itd_micro_frame(utr, fidx, itd, i);

			fidx++;                         /* preceed to next UTR iso frame              */

			if (fidx == IF_PER_UTR)         /* is the last scheduled micro-frame?         */
			{
				/* raise interrupt on completed               */
				itd->Transaction[i] |= ITD_IOC;
				break;
			}
		}

		itd_next = itd->next;               /* remember the next itd                      */

		// USB_debug("Link iTD 0x%x, %d\n", (int)itd, iso_ep->next_frame);
		/*
		 *  Link iTD to period frame list
		 */
		DISABLE_EHCI_IRQ();
		itd->sched_frnidx = iso_ep->next_frame;       /* remember it for reclamation scan */
		add_itd_to_iso_ep(iso_ep, itd);               /* add to software itd list         */
		itd->Next_Link = _PFList[itd->sched_frnidx];  /* keep the next link               */
		_PFList[itd->sched_frnidx] = ITD_HLNK_ITD(itd);
		iso_ep->next_frame = (iso_ep->next_frame + interval) % FL_SIZE;
		ENABLE_EHCI_IRQ();

		itd = itd_next;
	}

	_ehci->UCMDR |= HSUSBH_UCMDR_PSEN_Msk;      /* periodic list enable                   */
	return 0;

malloc_failed:

	while (!IS_NULL_PTR(itd_list))
	{
		itd = itd_list;
		itd_list = itd->next;
		free_ehci_iTD(itd);
	}
	return USBH_ERR_MEMORY_OUT;
}

static __inline void  add_sitd_to_iso_ep(ISO_EP_T *iso_ep, siTD_T *sitd)
{
	siTD_T   *p;

	sitd->next = NULL;

	if (IS_NULL_PTR(iso_ep->sitd_list))
	{
		iso_ep->sitd_list = sitd;
		return;
	}

	/*
	 * Find the tail entry of iso_ep->itd_list
	 */
	p = iso_ep->sitd_list;
	while (!IS_NULL_PTR(p->next))
	{
		p = p->next;
	}
	p->next = sitd;
}

static void  write_sitd_info(UTR_T *utr, siTD_T *sitd)
{
	UDEV_T     *udev = utr->udev;
	EP_INFO_T  *ep = utr->ep;               /* reference to isochronous endpoint          */
	uint32_t   buff_page_addr;
	int        xlen = utr->iso_xlen[sitd->fidx];
	int        scnt;

	sitd->Chrst = (udev->port_num << SITD_PORT_NUM_Pos) |
				  (udev->parent->iface->udev->dev_num << SITD_HUB_ADDR_Pos) |
				  ((ep->bEndpointAddress & 0xF) << SITD_EP_NUM_Pos) |
				  (udev->dev_num << SITD_DEV_ADDR_Pos);

	buff_page_addr = ptr_to_u32(utr->iso_buff[sitd->fidx]) & 0xFFFFF000;
	sitd->Bptr[0] = ptr_to_u32(utr->iso_buff[sitd->fidx]);
	sitd->Bptr[1] = buff_page_addr + 0x1000;

	scnt = (xlen + 187) / 188;

	if ((ep->bEndpointAddress & EP_ADDR_DIR_MASK) == EP_ADDR_DIR_IN)   /* I/O               */
	{
		sitd->Chrst |= SITD_XFER_IN;
		sitd->Sched = (1 << (scnt + 2)) - 1;
		sitd->Sched = (sitd->Sched << 10) | 0x1;
		//sitd->Sched <<= 1;
	}
	else
	{
		sitd->Chrst |= SITD_XFER_OUT;
		sitd->Sched = sitd_OUT_Smask[scnt-1];
		if (scnt > 1)
		{
			sitd->Bptr[1] |= (0x1 << 3);        /* Transaction position (TP)  01b: Begin  */
		}
		sitd->Bptr[1] |= scnt;                  /* Transaction count (T-Count)            */
	}

	if (sitd->fidx == IF_PER_UTR)
	{
		sitd->Sched |= SITD_IOC;
	}

	sitd->StsCtrl = (xlen << SITD_XFER_CNT_Pos) | SITD_STATUS_ACTIVE;

	sitd->BackLink = SITD_LIST_END;
}


static void ehci_sitd_adjust_schedule(siTD_T *sitd)
{
	siTD_T     *hlink = (siTD_T  *)((uint64_t)_PFList[sitd->sched_frnidx]);
	uint32_t   uframe_mask = 0x00;

	while (!IS_NULL_PTR(hlink) && !HLINK_IS_TERMINATED(hlink) && HLINK_IS_SITD(hlink))
	{
		hlink = SITD_PTR(hlink);
		if (hlink != sitd)
		{
			if ((hlink->Chrst & SITD_XFER_IO_Msk) == SITD_XFER_IN)
			{
				uframe_mask |= (hlink->Sched & 0xFF);         /* mark micro-frames used by IN S-mask   */
				uframe_mask |= ((hlink->Sched >> 8) & 0xFF);  /* mark micro-frames used by IN C-mask   */
			}
			else
			{
				uframe_mask |= (hlink->Sched & 0xFF);         /* mark micro-frames used by OUT S-mask  */
			}
		}
		hlink = SITD_PTR(hlink->Next_Link);
	}

	uframe_mask = uframe_mask | (uframe_mask << 8);           /* mark both S-mask and C-mask           */

	if (uframe_mask)
	{
		/*
		 *  Shift afterward one micro-frame until no conflicts.
		 */
		while (1)
		{
			if (sitd->Sched & uframe_mask)
			{
				sitd->Sched = (sitd->Sched & 0xFFFF0000) | ((sitd->Sched << 1) & 0xFFFF);
			}
			else
			{
				break;                      /* no conflit, done.                          */
			}
		}
	}
}


static int ehci_iso_split_xfer(UTR_T *utr, ISO_EP_T *iso_ep)
{
	EP_INFO_T  *ep = utr->ep;               /* reference to isochronous endpoint          */
	siTD_T     *sitd, *sitd_next, *sitd_list = NULL;
	int        i;
	int        fidx;                        /* index to the 8 iso frames of UTR           */

	if (IS_NULL_PTR(utr->udev->parent))
	{
		USB_error("siso xfer - parent lost!\n");
		return USBH_ERR_INVALID_PARAM;
	}

	/*------------------------------------------------------------------------------------*/
	/*  Allocate siTDs                                                                    */
	/*------------------------------------------------------------------------------------*/
	for (i = 0; i < IF_PER_UTR; i++)        /* allocate all siTDs required by UTR         */
	{
		sitd = alloc_ehci_siTD();
		if (IS_NULL_PTR(sitd))
			goto malloc_failed;

		if (IS_NULL_PTR(sitd_list))         /* link all siTDs                             */
		{
			sitd_list = sitd;
		}
		else
		{
			sitd->next = sitd_list;
			sitd_list = sitd;
		}
	}
	utr->td_cnt = IF_PER_UTR;

	/*------------------------------------------------------------------------------------*/
	/*  Fill and link all siTDs                                                           */
	/*------------------------------------------------------------------------------------*/

	utr->iso_sf = iso_ep->next_frame;
	fidx = 0;                               /* index to UTR iso frmes (total IF_PER_UTR)  */

	for (sitd = sitd_list; !IS_NULL_PTR(sitd); fidx++)
	{
		if (fidx >= IF_PER_UTR)             /* unlikely                                   */
		{
			USB_error("EHCI driver siTD bug!?\n");
			goto malloc_failed;
		}

		sitd->utr = utr;
		sitd->fidx = fidx;                   /* index to UTR's n'th IF_PER_UTR frame       */

		write_sitd_info(utr, sitd);

		sitd_next = sitd->next;              /* remember the next itd                      */

		// USB_debug("Link iTD 0x%x, %d\n", (int)itd, iso_ep->next_frame);
		/*
		 *  Link iTD to period frame list
		 */
		sitd->sched_frnidx = iso_ep->next_frame;      /* remember it for reclamation scan */
		DISABLE_EHCI_IRQ();
		ehci_sitd_adjust_schedule(sitd);
		add_sitd_to_iso_ep(iso_ep, sitd);             /* add to software itd list         */
		sitd->Next_Link = _PFList[sitd->sched_frnidx];/* keep the next link               */
		_PFList[sitd->sched_frnidx] = SITD_HLNK_SITD(sitd);
		iso_ep->next_frame = (iso_ep->next_frame + ep->bInterval) % FL_SIZE;
		ENABLE_EHCI_IRQ();

		sitd = sitd_next;
	}

	_ehci->UCMDR |= HSUSBH_UCMDR_PSEN_Msk;      /* periodic list enable                   */
	return 0;

malloc_failed:

	while (!IS_NULL_PTR(sitd_list))
	{
		sitd = sitd_list;
		sitd_list = sitd->next;
		free_ehci_siTD(sitd);
	}
	return USBH_ERR_MEMORY_OUT;
}

/*
 *  If it's an isochronous endpoint, quit current transfer via UTR or hardware EP.
 */
static int ehci_quit_iso_xfer(UTR_T *utr, EP_INFO_T *ep)
{
	ISO_EP_T   *iso_ep;
	iTD_T      *itd, *itd_next, *p;
	uint32_t   frnidx;
	uint32_t   now_frame;

	if (IS_NULL_PTR(ep))
	{
		if (IS_NULL_PTR(utr))
			return USBH_ERR_NOT_FOUND;

		if (IS_NULL_PTR(utr->ep))
			return USBH_ERR_NOT_FOUND;

		ep = nc_ptr(utr->ep);
	}

	if ((ep->bmAttributes & EP_ATTR_TT_MASK) != EP_ATTR_TT_ISO)
		return USBH_ERR_NOT_FOUND;          /* not isochronous endpoint                   */

	/*------------------------------------------------------------------------------------*/
	/*  It's an iso endpoint. Remove it as required.                                      */
	/*------------------------------------------------------------------------------------*/
	iso_ep = iso_ep_list;
	while (!IS_NULL_PTR(iso_ep))            /* Search all activated iso endpoints         */
	{
		if (iso_ep->ep == ep)
			break;
		iso_ep = iso_ep->next;
	}
	if (IS_NULL_PTR(iso_ep))
		return 0;                           /* should have been removed                   */

	itd = iso_ep->itd_list;                 /* get the first iTD from iso_ep's iTD list   */

	while (!IS_NULL_PTR(itd))               /* traverse all iTDs of itd list              */
	{
		itd_next = itd->next;               /* remember the next iTD                      */
		utr = itd->utr;

		/*--------------------------------------------------------------------------------*/
		/*  Remove this iTD from period frame list                                        */
		/*--------------------------------------------------------------------------------*/
		frnidx = itd->sched_frnidx;

		/*
		 *  Prevent to race with Host Controller. If the iTD to be removed is located in
		 *  current or next frame, wait until HC passed through it.
		 */
		while (1)
		{
			now_frame = (_ehci->UFINDR >> 3) & 0x3FF;
			if ((now_frame == frnidx) || (((now_frame+1)%1024) == frnidx))
				continue;
			break;
		}

		if (_PFList[frnidx] == ITD_HLNK_ITD(itd))
		{
			/* is the first entry, just change to next     */
			_PFList[frnidx] = itd->Next_Link;
		}
		else
		{
			p = ITD_PTR(_PFList[frnidx]);   /* find the preceding iTD                     */
			while ((ITD_PTR(p->Next_Link) != itd) && !IS_NULL_PTR(p))
			{
				p = ITD_PTR(p->Next_Link);
			}

			if (IS_NULL_PTR(p))             /* link list out of control!                  */
			{
				USB_error("ehci_quit_iso_xfer - An iTD lost reference to periodic frame list! 0x%x on %d\n", (int64_t)itd, frnidx);
			}
			else                            /* remove iTD from list                       */
			{
				p->Next_Link = itd->Next_Link;
			}
		}
		utr->td_cnt--;

		if (utr->td_cnt == 0)               /* All iTD of this UTR done                   */
		{
			utr->bIsTransferDone = 1;
			if (!IS_NULL_PTR(utr->func))
				utr->func(utr);
			utr->status = USBH_ERR_ABORT;
		}
		free_ehci_iTD(itd);
		itd = itd_next;
	}

	/*
	 *  Remove iso_ep from iso_ep_list
	 */
	remove_iso_ep_from_list(iso_ep);
	usbh_free_mem(iso_ep, sizeof(*iso_ep));      /* free this iso_ep                      */
	ep->hw_pipe = NULL;

	if (IS_NULL_PTR(iso_ep_list))
		_ehci->UCMDR &= ~HSUSBH_UCMDR_PSEN_Msk;

	return 0;
}


/// @endcond HIDDEN_SYMBOLS
